#include <errno.h>
#include <fcntl.h>
#include <libgen.h>
#include <limits.h>
#include <stdint.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <unistd.h>

#include <atomic>
#include <cstring>
#include <fstream>
#include <map>
#include <memory>
#include <mutex>
#include <queue>
#include <string>
#include <utility>
#include <vector>

class Utils {
 public:
  static bool IsFileExist(const std::string &path);
  static bool IsFileWritable(const std::string &path);
  static bool IsDir(const std::string &path);
  static bool CreateDir(const std::string &path);
  static std::string RealPath(const std::string &path);
  static std::string RelativeToAbsPath(const std::string &path);
  static std::string DirName(const std::string &path);
  static uint64_t GetClockMonotonicRawNs();
  static bool CreateDumpFile(const std::string &path);
  static bool IsSoftLink(const std::string &path);
  static uint64_t GetTid();
  static uint64_t GetPid();
};

bool Utils::IsFileExist(const std::string &path) {
  if (path.empty() || path.size() > PATH_MAX) {
    return false;
  }
  return (access(path.c_str(), F_OK) == 0) ? true : false;
}

bool Utils::IsFileWritable(const std::string &path) {
  if (path.empty() || path.size() > PATH_MAX) {
    return false;
  }
  return (access(path.c_str(), W_OK) == 0) ? true : false;
}

bool Utils::IsDir(const std::string &path) {
  if (path.empty() || path.size() > PATH_MAX) {
    return false;
  }
  struct stat st = {0};
  int ret = lstat(path.c_str(), &st);
  if (ret != 0) {
    return false;
  }
  return S_ISDIR(st.st_mode) ? true : false;
}

bool Utils::CreateDir(const std::string &path) {
  if (path.empty() || path.size() > PATH_MAX) {
    return false;
  }
  if (IsFileExist(path)) {
    return IsDir(path) ? true : false;
  }
  size_t pos = 0;
  while ((pos = path.find_first_of('/', pos)) != std::string::npos) {
    std::string base_dir = path.substr(0, ++pos);
    if (IsFileExist(base_dir)) {
      if (IsDir(base_dir)) {
        continue;
      } else {
        return false;
      }
    }
    if (mkdir(base_dir.c_str(), 0750) != 0) {
      return false;
    }
  }
  return (mkdir(path.c_str(), 0750) == 0) ? true : false;
}

std::string Utils::RealPath(const std::string &path) {
  if (path.empty() || path.size() > PATH_MAX) {
    return "";
  }
  char realPath[PATH_MAX] = {0};
  if (realpath(path.c_str(), realPath) == nullptr) {
    return "";
  }
  return std::string(realPath);
}

std::string Utils::RelativeToAbsPath(const std::string &path) {
  if (path.empty() || path.size() > PATH_MAX) {
    return "";
  }
  if (path[0] != '/') {
    char pwd_path[PATH_MAX] = {0};
    if (getcwd(pwd_path, PATH_MAX) != nullptr) {
      return std::string(pwd_path) + "/" + path;
    }
    return "";
  }
  return std::string(path);
}

std::string Utils::DirName(const std::string &path) {
  if (path.empty()) {
    return "";
  }
  std::string temp_path = std::string(path.begin(), path.end());
  char *path_c = dirname(const_cast<char *>(temp_path.data()));
  return path_c ? std::string(path_c) : "";
}

uint64_t Utils::GetClockMonotonicRawNs() {
  struct timespec ts = {0};
  clock_gettime(CLOCK_MONOTONIC_RAW, &ts);
  return static_cast<uint64_t>(ts.tv_sec) * 1000000000 +
         static_cast<uint64_t>(ts.tv_nsec);  // To convert to nanoseconds, it
                                             // needs to be 1000000000.
}

bool Utils::CreateDumpFile(const std::string &path) {
  if (path.empty() || path.size() > PATH_MAX || !CreateDir(DirName(path))) {
    std::cout << "CreateDir failed" << std::endl;
    return false;
  }
  std::ofstream output_file(path);
  output_file.close();
  if (chmod(path.c_str(), S_IRUSR | S_IWUSR) == -1) {
    return false;
  }
  return true;
}

bool Utils::IsSoftLink(const std::string &path) {
  if (path.empty() || path.size() > PATH_MAX || !IsFileExist(path)) {
    return false;
  }
  struct stat st {};
  if (lstat(path.c_str(), &st) != 0) {
    return false;
  }
  return S_ISLNK(st.st_mode);
}

uint64_t Utils::GetTid() {
#if !defined(_WIN32) && !defined(_WIN64) && !defined(__ANDROID__) && \
    !defined(ANDROID) && !defined(__APPLE__)
  static thread_local uint64_t tid = static_cast<uint64_t>(syscall(SYS_gettid));
#else
  static thread_local uint64_t tid = 0;
#endif
  return tid;
}